/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "layermap.h"
#include "layermapconfig.h"
#include "configfile.h"
#include "configwidget.h"
#include "workerprecachemap.h"
#include "workerclearcache.h"
#include "image.h"
#include "filenavigator.h"
#include "ll-utm/LatLong-UTMconversion.h"

#define FLOAT_OFFSET 1000000.0

extern ConfigWidget* conf;

bool chunkKey::operator==(chunkKey key ) const
{
    return (key.x == x) && (key.y == y) && (key.zoom == zoom);
}

uint qHash( const chunkKey& key )
{
    // We "hash" the key by using 32bits in this way:
    // bit 0-10 (11 bits) : the x
    // bit 11-20 (11 bits) : the y
    // bit 21-31 (10 bits) : the zoom
    return ( (key.zoom & 0x000003ff) << 21 ) |
           ( (key.y    & 0x000007ff) << 11 ) |
             (key.x    & 0x000007ff);
}

#define CURRENT_MAP_VERSION "1.2.0"

// Say how many chunk we can load for each image
#define MAX_NUMBER_OF_CHUNKS 9

LayerMap::LayerMap(QString t, QString d):
        Layer(t,d)
{
    loaded = false;
    calibrated = false;
    img_filename = "";
    image_rect = QRect();
    max_zoom_factor = 1;
    ready_for_draw = true;

    pointsXY.append( QPoint(0,0) );
    pointsXY.append( QPoint(0,0) );
    pointsXY.append( QPoint(0,0) );

    pointsLL.append( QPointF(0.0,0.0) );
    pointsLL.append( QPointF(0.0,0.0) );
    pointsLL.append( QPointF(0.0,0.0) );

    allocated_chunks = 0;

    use_matrix = QTransform();
}

LayerMap::~LayerMap()
{
    // Save calibration, if it has changed
    if ( modified )
        saveCalibration();
    // Delete all chunks
    foreach (mapChunk* tmp, chunk_pool )
        delete tmp;
}

mapChunk* LayerMap::allocateChunk()
{
    if ( allocated_chunks < MAX_NUMBER_OF_CHUNKS )
    {
        allocated_chunks++;
        mapChunk* tmp = new mapChunk;
        tmp->image = QImage( CHUNK_SIZE, CHUNK_SIZE, QImage::Format_RGB888 );
        tmp->image.fill(0);
        tmp->last_used = QDateTime();
        // set the key as invalid
        tmp->key.zoom = tmp->key.x = tmp->key.y = -1;
        return tmp;
    }
    return NULL;
}

void LayerMap::clearCache()
{
    WorkerClearCache runner( img_filename, cache_basename,
                              chunks_cols, chunks_rows );
    runner.run( tr("Clearing the cache..."));
}

FreeTrackWidget* LayerMap::getConfigureLayer()
{
    return new LayerMapConfig(0, this);
}

FreeTrackWidget* LayerMap::getLayerLabel()
{
    return NULL;
}


// Bondary: is the SCREEN (device screen) expressed in map cohordinates.
//      Example: boundary(100,100)-(1500,1500) means that a portion of the map width=1400, height=1400
//               placed at 100,100 shall FIT into the device screen.
// placeHere: offset to paint the image to (always refers to top left corner of image)
void LayerMap::paintUncalibrated( QPainter& painter, QPoint placeHere, QRect visible_area )
{
    // Lets not paint if we are not ready to
    if ( !ready_for_draw )
        return;

    // (1) Sanity checks...
    // Dont do anything on a visible_area that does not intersect the image_area,
    // also, do not print anything if the visible_area is less than 1px.
    if ( !visible_area.intersects( image_rect ) ||
         (visible_area.width() <= 1) ||
         (visible_area.height() <= 1) )
        return;

    // (2) Calculate the zoom factor required...
    // The zoom factor is always integer. So for any "intermediate" zoom we just keep using the old chunks.
    // This works, because we have a 3x3 chunks grid where each chunk is bigger than the screen...
    // Also, make sure zoom_factor is bounded between 1 and max_zoom_factor, which is the zoom where the entire
    // image is loaded into the chunk matrix.
    int zoom_factor = calcZoomFactor( visible_area.width(), visible_area.height(), CHUNK_SIZE  );
    if ( zoom_factor > max_zoom_factor )
        zoom_factor = max_zoom_factor;

    // (3) Restrict the visible_area...
    // Use only the intersection between image_rect and visible_area, we do not want to paint anything
    // outside this intersection. Note: this will never be empty due to the (1) check we did above.
    visible_area = visible_area.intersected( image_rect );

    // (4) Find the central chunk...
    // Identify the chunk number which shall be our new central chunk
    QPoint center = visible_area.center();
    int center_x = center.x() / (CHUNK_SIZE * zoom_factor);
    int center_y = center.y() / (CHUNK_SIZE * zoom_factor);
    int offset_x = (center.x() % (CHUNK_SIZE * zoom_factor)) / zoom_factor;
    int offset_y = (center.y() % (CHUNK_SIZE * zoom_factor)) / zoom_factor;


    // (5) Get ready to draw...
    painter.save();
    // Translate the painter to compensate for the placeHere offset...
    painter.translate( placeHere );
    // Setup the clipping region to the restricted visible_area rect to avoid unecessary paintings
    painter.setClipRect( visible_area );
    // Scale the painter to represent this zoom factor
    painter.scale( zoom_factor, zoom_factor );

    // (6) Print all loaded chunks...
    // First we calculate which chunks we need to draw...
    MissingChunkKeys = calculateChunkKeys( center_x, center_y, zoom_factor, offset_x, offset_y  );
    // Iterate all ChunkKeys...
    foreach ( chunkKey key, MissingChunkKeys )
    {
        QPoint pt( key.x, key.y );
        mapChunk* tmp = chunk_pool.value( key, NULL );
        // Check if we have an already loaded chunk for this key...
   //     QPointF tpt = (pt*CHUNK_SIZE) * use_matrix;
        if ( tmp != NULL )
        {
            // Draw the chunk at the proper position...
            painter.drawImage( pt * CHUNK_SIZE, tmp->image);
//            painter.drawImage( tpt, tmp->image.transformed( use_matrix ) );
            // Update chunk timestamp...
            tmp->last_used = QDateTime::currentDateTime();
            // Remove the key, we have found it, no need to process this any more.
            MissingChunkKeys.removeOne( key );
        }
        else
        {   // chunk is missing, so print something else right now
            painter.drawPixmap( QRect( pt * CHUNK_SIZE, QSize(CHUNK_SIZE,CHUNK_SIZE)),
                                QPixmap(":/icons/cancel.png"));
        }
//        painter.drawRect( QRect( pt * CHUNK_SIZE, QSize(CHUNK_SIZE,CHUNK_SIZE) ) );
    }

    if ( !MissingChunkKeys.isEmpty() )
    {   // we have some more chunks that needs to be loaded...
        ready_for_draw = false;
        QTimer::singleShot( 0, this, SLOT(loadMissingChunks()) );
    }
    painter.restore();
}

QList<chunkKey> LayerMap::calculateChunkKeys( int cx, int cy, int zoom, int ox, int oy )
{
    QList<chunkKey> key_list;
    chunkKey key;
    int max_x = chunks_cols.value(zoom)-1;
    int max_y = chunks_rows.value(zoom)-1;

    // Here, we need to put in the list the four chunks which are visible. Since each chunk is
    // as big as the screen, we can safely say that we at most must display four chunks.

    // The first one is always the center chunk:
    key.zoom = zoom;
    key.x = cx;
    key.y = cy;
    key_list.append( key );

    // The other three depend on which "quadrant" the center chunk is.
    bool left = false,
         top = false,
         right = false,
         bottom = false,
         top_left = false,
         top_right = false,
         bottom_left = false,
         bottom_right = false;

    // Identify the quadrant we are in:
    if ( (ox <= CHUNK_SIZE/2) && (oy <= CHUNK_SIZE/2) ) // top-left quadrant
    { // add x-1, y-1 and the top-left fourth
        left = top = top_left = true;
    }
    else if ( (ox > CHUNK_SIZE/2) && (oy <= CHUNK_SIZE/2) ) // top-right quadrant
    { // add x+1, y-1 and the top-right fourth
        right = top = top_right = true;
    }
    else if ( (ox <= CHUNK_SIZE/2) && (oy > CHUNK_SIZE/2) ) // bottom-left quadrant
    { // add x-1, y+1 and the bottom-left fourth
        left = bottom = bottom_left = true;
    }
    else if ( (ox > CHUNK_SIZE/2) && (oy > CHUNK_SIZE/2) ) // bottom-right quadrant
    { // add x+1, y+1 and the bottom-right fourth
        right = bottom = bottom_right = true;
    }

    // Now populate the chunks
    if ( left && (cx>0) )
    {
        key.x = cx-1;
        key.y = cy;
        key_list.append( key );
    }
    if ( top && (cy>0) )
    {
        key.y = cy-1;
        key.x = cx;
        key_list.append( key );
    }
    if ( right && (cx<max_x) )
    {
        key.x = cx+1;
        key.y = cy;
        key_list.append( key );
    }
    if ( bottom && (cy<max_y) )
    {
        key.y = cy+1;
        key.x = cx;
        key_list.append( key );
    }
    if ( top_left && (cx>0) && (cy>0) )
    {
        key.y = cy-1;
        key.x = cx-1;
        key_list.append( key );
    }
    if ( top_right && (cx<max_x) && (cy>0) )
    {
        key.y = cy-1;
        key.x = cx+1;
        key_list.append( key );
    }
    if ( bottom_left && (cx>0) && (cy<max_y) )
    {
        key.y = cy+1;
        key.x = cx-1;
        key_list.append( key );
    }
    if ( bottom_right && (cx<max_x) && (cy<max_y) )
    {
        key.y = cy+1;
        key.x = cx+1;
        key_list.append( key );
    }

    return key_list;
}

void LayerMap::loadMissingChunks()
{
    QPoint pt;
    QString cachefile;
    mapChunk* new_chunk = NULL;
    // Create an invalid key for later reference
    chunkKey invalid_key;
    invalid_key.x = invalid_key.y = invalid_key.zoom = -1;
    // Iterate on all missing chunks...
    foreach ( chunkKey key, MissingChunkKeys )
    {
        // Check if the cached file exists..
        cachefile = cache_basename.arg( key.x).arg( key.y ).arg( key.zoom );
        if ( QFile::exists( cachefile ) )
        {
            // Finding a good chunk to use is not that simple...
            // First we try to allocate a new chunk (so we do not loose any already loaded chunk we might reuse soon):
            new_chunk = allocateChunk();
            // But what if we have already allocated all the available chunks?
            if ( (new_chunk == NULL) && (chunk_pool.size() > 0 ) )
            {
                // Then we must find a chunk to "unload".
                // Any "invalid" chunk can be used right now:
                if ( chunk_pool.count( invalid_key ) > 0 )
                    new_chunk = chunk_pool.take( invalid_key );
                else // but if we do not have any, we need to find the one we have not used since more time:
                {
                    QDateTime oldest = QDateTime::currentDateTime().addSecs(1); // +1 to ensure any other last_used must be smaller
                    foreach (mapChunk* tmp, chunk_pool)
                    {   // Has this chunk been used the last time before the one we have already saved?
                        if ( tmp->last_used < oldest )
                        {   // store this chunk as the "oldest used" one
                            oldest = tmp->last_used;
                            new_chunk = tmp;
                        }
                    }
                    // We remove the chunk from the pool, and the reason is that we will have to put it back with a
                    // different key anyway!
                    if ( new_chunk != NULL )
                        chunk_pool.remove( new_chunk->key );
                }
            }
            // Proceed, but only if we have found a proper chunk to use...
            if ( new_chunk != NULL )
            {
                Image* image = Image::LoadValid( cachefile );
                if ( image != NULL )
                {
                    // We have a valid chunk, so lets update its timestamp:
                    new_chunk->last_used = QDateTime::currentDateTime();
                    // Update its key...
                    new_chunk->key = key;
                    // Load the chunk image...
                    image->readImage( &new_chunk->image );
                    Image::FreeImage( image );
                }
                else // sorry but chunk is invalid!
                {   // Make sure the key is invalid
                    new_chunk->key = invalid_key;
                }
                // Put the chunk into the pool anyway, even if it is not valid.
                // note that invalid chunks will be stored as multi-keys, and valid chunks
                // should never be multiple, or we would have already used them in the paint function.
                chunk_pool.insertMulti( new_chunk->key,  new_chunk );
            }
        }
    }
    // Reset draw stopper
    ready_for_draw = true;
    emit redraw();
}

bool LayerMap::cacheRebuild()
{
    WorkerPrecacheMap runner( img_filename, cache_basename,
                              chunks_cols, chunks_rows );
    return runner.run( tr("Precaching map...") );
}

QRect LayerMap::getRect()
{
    return image_rect;
}

void LayerMap::paintLayer(QPainter&painter, const QTransform& matrix, const QRectF& clip)
{
    if ( (loaded && calibrated) )
    {        
        painter.save();


        use_matrix = image_to_wgs84 * matrix;
        QRect new_clip = wgs84_to_image.mapRect( clip ).toRect().adjusted(-1,-1,1,1);

        // REMEMBER: you draw here in simple cohordinates... This transformation gets rid of LAT/LON!!!
        painter.setWorldTransform( use_matrix, true );
        // Now print the uncalibrated map...
        paintUncalibrated(painter, QPoint(0,0), new_clip );

        painter.restore();

        painter.setPen( QPen( QColor( 255,255,255 )));
        QRect rrr = use_matrix.mapRect( image_rect );
        painter.drawRect( rrr );

        use_matrix = QTransform();
    }
}

bool LayerMap::loadMap( const QString& filename)
{    
    // We have not loaded yet...
    loaded = false;
    // set we are not calibrated
    calibrated = false;

    // We need some infos on our filename
    QFileInfo fileinfo( filename );

    // Does the image exist?
    if ( !fileinfo.exists() )
    {
        QMessageBox::critical( qApp->activeWindow(), tr("Open map"), tr("File does not exist!"));
        return false;
    }

    // Check image and load size...
    int image_width;
    int image_height;
    if ( !Image::getSize( filename, image_width, image_height ) )
        return false;
    image_rect = QRect(0,0,image_width, image_height);

    // Setup filename:
    img_filename = filename;

    // Determine maximum zoom factor (the zoom that will fit the entire image into the chunks)
    // We support MAXIMUM zoom == 256, so we cannot use higher zooms.
    max_zoom_factor = calcZoomFactor( image_width, image_height, CHUNK_SIZE * 2 );

    // Calculate n. of chunks that fits into the image at every zoom level
    for (int z = 1; z <= max_zoom_factor; z *= 2)
    {
        chunks_cols.insert(z, ceil( (qreal)image_width / (qreal)(CHUNK_SIZE*z) ) );
        chunks_rows.insert(z, ceil( (qreal)image_height / (qreal)(CHUNK_SIZE*z) ) );
    }

    // Build the template string to generate the chunks filenames
    QString cache_path = FileNavigator::map_cache_path();
    QString cache_folder = cache_path + "/" + fileinfo.fileName();
    // Create folder if it is missing
    if ( !QFile::exists( cache_folder ) )
        QDir(cache_path).mkdir( fileinfo.fileName() );
    // done!
    cache_basename = cache_folder + "/%1_%2_%3.jpg";

    loaded = true;
    // We have loaded at this point, the following code will not change that...

    // Now, down to map calibration...
    QList<QPoint> new_pointsXY;
    QList<QPointF> new_pointsLL;
    QString tmp_filename = filename + ".cal";
    QString tmp_title;
    QString tmp_description;
    // 1) if we already have our generated "cal file", and if it is valid... load it!
    if ( !(QFile::exists( tmp_filename ) && loadCalibration( tmp_filename, new_pointsXY, new_pointsLL, tmp_title, tmp_description )) )
    {
        bool save_calibration = false;
        // 2) check if there is an Ozi explorer MAP file
        tmp_filename = fileinfo.absolutePath() + "/" + fileinfo.completeBaseName() + ".map";
        if ( !(save_calibration = (QFile::exists( tmp_filename ) && loadOziMap( tmp_filename, new_pointsXY, new_pointsLL )) ) )
        {
            // 3) check if there is an CompeGPS IMP file
            tmp_filename = fileinfo.absolutePath() + "/" + fileinfo.completeBaseName() + ".imp";
            save_calibration = QFile::exists( tmp_filename ) && loadCompeImp( tmp_filename, new_pointsXY, new_pointsLL );
        }
        // If we where successful, save the calibration!
        if ( save_calibration )
            setPoints( new_pointsXY, new_pointsLL );
    }
    else
    {
        pointsXY = new_pointsXY;
        pointsLL = new_pointsLL;
        setTitle( tmp_title != QString("") ? tmp_title : fileinfo.baseName() );
        setDescription( tmp_description != QString("") ? tmp_description : tr("map loaded from file") );
        calibrateMatrix();
    }

    // If map precaching is NOT disabled...
    if ( conf->getPrecacheMaps() )
    {
        if ( !cacheRebuild() )
            QMessageBox::critical( qApp->activeWindow(),tr("Precaching map"),
                                                        tr("Caching incomplete! Map will be cached when used."));
    }
    return true;
}

int LayerMap::calcZoomFactor(qreal dim1, qreal dim2, qreal size )
{
    int ret = qMax( 1,(int)ceil( qMax( dim1,dim2 ) / size ) );
    // Ensure max_zoom_factor is 1,2,4,8,16,32,64,128 or 256 ONLY
    return ( ret > 256 ) ? 256 :
           ( ret > 128 ) ? 256 :
           ( ret > 64 ) ? 128 :
           ( ret > 32 ) ? 64 :
           ( ret > 16 ) ? 32:
           ( ret > 8 ) ? 16 :
           ( ret > 4 ) ? 8 :
           ( ret > 2 ) ? 4 : ret;
}

void LayerMap::saveCalibration()
{
    QString tmp_filename = img_filename + ".cal";
    ConfigFile config( tmp_filename, QIODevice::WriteOnly );

    config.writeRow( "title", title );
    config.writeRow( "description", description );

    int np = pointsXY.size();
    for (int p = 0; p < np; p++ )
    {
        QPoint xy = pointsXY.at(p);
        QPointF ll = pointsLL.at(p);
        config.writeRow( QString("point%1").arg(p),
                         QString("%1,%2 %3,%4").arg(xy.x())
                                               .arg(xy.y())
                                               .arg(ll.y())
                                               .arg(ll.x()) );
    }
    config.writeRow("version",CURRENT_MAP_VERSION);
    modified = false;
}

void LayerMap::calibrateMatrix()
{
/*    // This code will create the transformation matrix than converts the image (0,0,0+width,0+height) to
    // the proper lon/lat area
    qreal X1 = pointsXY.at(0).x();
    qreal Y1 = pointsXY.at(0).y();
    qreal X2 = pointsXY.at(1).x();
    qreal Y2 = pointsXY.at(1).y();
    qreal X3 = pointsXY.at(2).x();
    qreal Y3 = pointsXY.at(2).y();

    // Due to limited Symbian float precision, we need to multiply this by the OFFSET
    qreal Xi1 = FLOAT_OFFSET*pointsLL.at(0).x();
    qreal Yi1 = FLOAT_OFFSET*pointsLL.at(0).y();
    qreal Xi2 = FLOAT_OFFSET*pointsLL.at(1).x();
    qreal Yi2 = FLOAT_OFFSET*pointsLL.at(1).y();
    qreal Xi3 = FLOAT_OFFSET*pointsLL.at(2).x();
    qreal Yi3 = FLOAT_OFFSET*pointsLL.at(2).y();*/

    qreal Xi1 = pointsXY.at(0).x();
    qreal Yi1 = pointsXY.at(0).y();
    qreal Xi2 = pointsXY.at(1).x();
    qreal Yi2 = pointsXY.at(1).y();
    qreal Xi3 = pointsXY.at(2).x();
    qreal Yi3 = pointsXY.at(2).y();

    // Due to limited Symbian float precision, we need to multiply this by the OFFSET
    qreal X1 = FLOAT_OFFSET*pointsLL.at(0).x();
    qreal Y1 = FLOAT_OFFSET*pointsLL.at(0).y();
    qreal X2 = FLOAT_OFFSET*pointsLL.at(1).x();
    qreal Y2 = FLOAT_OFFSET*pointsLL.at(1).y();
    qreal X3 = FLOAT_OFFSET*pointsLL.at(2).x();
    qreal Y3 = FLOAT_OFFSET*pointsLL.at(2).y();

    qreal dXi31 = Xi3 - Xi1;
    qreal dYi31 = Yi3 - Yi1;
    qreal dX31 = X3 - X1;
    qreal dY31 = Y3 - Y1;
    qreal dX21 = X2 - X1;
    qreal dY21 = Y2 - Y1;
    qreal dXi21 = Xi2 - Xi1;
    qreal dYi21 = Yi2 - Yi1;

    // Here we need to remove the offset...
    qreal m21 = ( dX31 * dXi21 - dX21 * dXi31 ) / ( dY21 * dX31 - dX21 * dY31 ) * FLOAT_OFFSET;
    qreal m12 = ( dYi21 * dY31 - dY21 * dYi31 ) / ( dX21 * dY31 - dX31 * dY21 ) * FLOAT_OFFSET;
    // Here we need to remove the offset again...
    qreal m11 = dXi31 * FLOAT_OFFSET / dX31 - m21 * dY31 / dX31 ;
    qreal m22 = dYi31 * FLOAT_OFFSET / dY31 - m12 * dX31 / dY31;
    // Here we need to remove the offset again...
    qreal m31 = Xi1 - m11 * X1 / FLOAT_OFFSET - m21 * Y1 / FLOAT_OFFSET;
    qreal m32 = Yi1 - m22 * Y1 / FLOAT_OFFSET - m12 * X1 / FLOAT_OFFSET;

  /*  // Here we need to remove the offset again...
    qreal m11 = dXi31/ FLOAT_OFFSET / dX31 - m21 * dY31 / dX31 ;
    qreal m22 = dYi31/ FLOAT_OFFSET / dY31 - m12 * dX31 / dY31;
    // Here we need to remove the offset again...
    qreal m31 = Xi1/ FLOAT_OFFSET - m11 * X1 - m21 * Y1;
    qreal m32 = Yi1/ FLOAT_OFFSET - m22 * Y1 - m12 * X1;
*/
    qreal m13 = 0;
    qreal m23 = 0;
    qreal m33 = 1;

//    image_to_wgs84 = QTransform( m11, m12, m13, m21, m22, m23, m31, m32, m33 );
//    wgs84_to_image = image_to_wgs84.inverted();
    wgs84_to_image  = QTransform( m11, m12, m13, m21, m22, m23, m31, m32, m33 );
    image_to_wgs84 = wgs84_to_image.inverted();

    calibrated = true;
}

void LayerMap::getPointsXY(QList<QPoint>& pts)
{
    pts = pointsXY;
}

void LayerMap::getPointsLL(QList<QPointF>& pts)
{
    pts = pointsLL;
}

void LayerMap::setPoints(const QList<QPoint>& xy, const QList<QPointF>& ll)
{
    int xys = xy.size();
    int lls = ll.size();

    if ( (xys == lls) && (xys >= 3) )
    {
        pointsXY = xy;
        pointsLL = ll;
    }
    modified = true;
    saveCalibration();
    calibrateMatrix();
}

bool LayerMap::loadOziMap(const QString &map_filename, QList<QPoint> &new_pointsXX, QList<QPointF> &new_pointsLL)
{
    bool ret = false;
    // Check if file exists
    if ( (map_filename != "") && QFile::exists( map_filename) )
    {
        QFile file(map_filename);
        // Open file and try to read it
        if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
        {


            // Read points:
// Point01,xy,  223,  510,in, deg,  45,    0744,N,   7,   61447,W, grid,   ,           ,           ,N
// Point01,xy, 3757, 2377,in, deg,    ,        ,N,    ,        ,W, grid, 33,     369962,    5151606,N

 QRegExp rx_pt(
         // PointAB %1
 "^\\s*point(\\d+)\\s*,"\
  "\\s*xy\\s*,"\
  // imageX %2
  "\\s*(\\d*)\\s*,"\
   // imageY %3
  "\\s*(\\d*)\\s*,"\
  "\\s*in\\s*,\\s*deg\\s*,"\
  // main LAT degrees %4
  "\\s*"REGEXP_NUMBER"\\s*,"\
  // min/sec LAT degrees %5
  "\\s*"REGEXP_NUMBER"\\s*,"\
   // north/south %6
  "\\s*([ns])\\s*,"\
  // main LON degrees %7
  "\\s*"REGEXP_NUMBER"\\s*,"\
  // min/sec LON degrees %8
  "\\s*"REGEXP_NUMBER"\\s*,"\
  // east/west %9
  "\\s*([we])\\s*,"\
  "\\s*grid\\s*,"\
  // UTM grid n. %10
  "\\s*(\\d*)\\s*,"\
  // UTM easting %11
  "\\s*(\\d*)\\s*,"\
  // UTM norting % 12
  "\\s*(\\d*)\\s*,"\
  // UTM zone letter &13
  "\\s*([a-z]).*$");


            QRegExp rx_mmpxy("^\\s*mmpxy\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+).*$");

            QRegExp rx_mmpll("^\\s*mmpll\\s*,\\s*(\\d+)\\s*,\\s*"REGEXP_NUMBER"\\s*,\\s*"REGEXP_NUMBER".*$");
            QList<QPoint> xy_list;
            QList<QPointF> ll_list;

            QString row;

            // Locate the points (Point01 to Point30):
            bool found = false;
            while ( !found && !file.atEnd() )
            {
                row = file.readLine().toLower();
                found = row.left(5) == "point";
            }

            // If we found the point section, load them all!
            while ( found && !file.atEnd() )
            {
                if ( rx_pt.indexIn( row ) > -1 )
                {
                    QString pt_n = rx_pt.cap(1);
                    QString x_str = rx_pt.cap(2);
                    QString y_str = rx_pt.cap(3);
                    if ( (x_str.trimmed() != "") && (y_str.trimmed() != "") )
                    {
                        QPoint pt( x_str.toInt(), y_str.toInt() );
                        QPointF pt_ll( 180, 180 );

                        QString lat = QString("%1.%2").arg( rx_pt.cap(4)).arg( rx_pt.cap(5));
                        QString lon = QString("%1.%2").arg( rx_pt.cap(7)).arg( rx_pt.cap(8));
                        if ( (lat.trimmed() != ".") && (lon.trimmed() != ".") )
                        {
                            pt_ll = QPointF( lon.toDouble(), lat.toDouble() );
                            QString lat_direction = rx_pt.cap(6);
                            QString lon_direction = rx_pt.cap(9);
                            if ( lat_direction == "s" )
                                pt_ll.setY( pt_ll.y() * -1.0 );
                            if ( lon_direction == "e" )
                                pt_ll.setX( pt_ll.x() * -1.0 );
                        }
                        else // go with UTM (if any)
                        {
                            QString utm_zone = rx_pt.cap(10) + rx_pt.cap(13).toUpper();
                            QString utm_easting = rx_pt.cap(11);
                            QString utm_norting = rx_pt.cap(12);
                            if ( (utm_easting.trimmed() != "" ) && (utm_norting.trimmed() != "" ) )
                            {
                                qreal easting = utm_easting.toDouble();
                                qreal northing = utm_norting.toDouble();
                                double qlat = 0, qlon = 0;
                                UTMtoLL( 23, northing,
                                             easting,
                                             utm_zone.toAscii(),
                                             qlat,qlon);
                                pt_ll = QPointF( qlon, qlat );
                            }
                        }
                        if ( pt_ll.x() < 180.0 )
                        {
                            xy_list.append( pt );
                            ll_list.append( pt_ll );
                        }
                    }
                }
                row = file.readLine().toLower();
                found = row.left(5) == "point";
            }

            // Locate the moving maps scetion (if any) (MMPXY or MMPLL):
            found = false;
            while ( !found && !file.atEnd() )
            {
                row = file.readLine().toLower();
                found = (row.left(5) == "mmpxy") || (row.left(5) == "mmpll");
            }

            // If we found the moving map section, load it!
            QHash<QString, QPoint> tmp_xy;
            QHash<QString, QPointF> tmp_ll;
            while ( !file.atEnd() )
            {
                if ( found )
                {
                    if ( rx_mmpxy.indexIn( row ) > -1 )
                    {
                        QString n = rx_mmpxy.cap(1);
                        QString x = rx_mmpxy.cap(2);
                        QString y = rx_mmpxy.cap(3);
                        tmp_xy.insert( n, QPoint( x.toInt(), y.toInt() ) );
                    }
                    else if ( rx_mmpll.indexIn( row ) > -1 )
                    {
                        QString n = rx_mmpll.cap(1);
                        QString lon = rx_mmpll.cap(2);
                        QString lat = rx_mmpll.cap(3);
                        tmp_ll.insert( n, QPointF( lon.toDouble(), lat.toDouble() ) );

                    }
                }
                row = file.readLine().toLower();
                found = (row.left(5) == "mmpxy") || (row.left(5) == "mmpll");
            }

            foreach ( QString key, tmp_xy.keys() )
            {
                if ( tmp_ll.value(key, QPointF() ) != QPointF() )
                {
                    xy_list.append( tmp_xy.value( key ) );
                    ll_list.append( tmp_ll.value( key ) );
                }
            }
            // We want at least 3 points to proceed...
            int np = xy_list.count();
            if ( np >= 3 )
            {
                new_pointsXX = xy_list;
                new_pointsLL = ll_list;
                ret = true;
            } // if al least 3 points
        } // if open ok
    } // if file exists
    return ret;
}

bool LayerMap::loadCalibration(const QString &cal_filename, QList<QPoint> &new_pointsXX, QList<QPointF> &new_pointsLL, QString& t, QString &d)
{
    QFileInfo qfi( cal_filename );
    // check extension...
    if ( qfi.suffix() != "cal" )
        return false;

    // Open config file
    ConfigFile config( cal_filename, QIODevice::ReadOnly );
    // Proceed only if calibration file exist
    if ( config.getStatus() )
    {
        QString tmp;
        t = config.readRow( "title" );
        d = config.readRow( "description" );

        QRegExp rx_point("\\s*(\\d+),(\\d+)\\s+"REGEXP_NUMBER","REGEXP_NUMBER"\\s*$");

        new_pointsXX.clear();
        new_pointsLL.clear();
        for (int p = 0; p < MAP_MAX_POINTS; p++ )
        {
            QString row_name = QString("point%1").arg(p);
            if ( config.readRow( row_name, rx_point ) )
            {
                new_pointsXX.append( QPoint( ((QString)rx_point.cap(1)).toInt(),
                                         ((QString)rx_point.cap(2)).toInt() ) );
                new_pointsLL.append( QPointF( ((QString)rx_point.cap(4)).toDouble(),
                                         ((QString)rx_point.cap(3)).toDouble() ) );
            }
        }
        // cal file is considered valid ONLY if we got at least 3 points...
        return (new_pointsXX.size() >=3 );
    }
    return false;
}

bool LayerMap::loadCompeImp(const QString &imp_filename, QList<QPoint> &new_pointsXX, QList<QPointF> &new_pointsLL)
{
    bool ret = false;
    // Check if file exists
    if ( (imp_filename != "") && QFile::exists( imp_filename) )
    {
        QFile file(imp_filename);
        // Open file and try to read it
        if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
        {
            QString row;

            bool found = false;
            // Look for the <Calibration> section...
            while ( !file.atEnd() )
            {
                row = file.readLine();
                if ( row.contains("<Calibration>") )
                {
                    found = true;
                    break;
                }
            }
            if ( found )
            {
                // Now we can read all the points...
                // P0=2568.10000000,1300.20000000,A,7.39803333,45.11053333
                // P0=258.60000000,5737.20000000,32T,340000.00000000,4968000.00000000
                QRegExp rx_pt("^\\s*P\\d+\\s*=\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\d+\\.?\\d*)\\s*,\\s*(\\w+)\\s*,\\s*"REGEXP_NUMBER"\\s*,\\s*"REGEXP_NUMBER"\\s*$");
                QList<QString> x_list;
                QList<QString> y_list;
                QList<QString> lon_list;
                QList<QString> lat_list;
                QList<QString> zone_list;
                for (int pts = 0; pts < 30; pts++ )
                {
                    row = file.readLine();
                    rx_pt.indexIn( row );
                    QString x = rx_pt.cap(1);
                    QString y = rx_pt.cap(2);
                    QString utm_zone = rx_pt.cap(3);
                    QString lat = QString("%1").arg( rx_pt.cap(5));
                    QString lon = QString("%1").arg( rx_pt.cap(4));
                    if ( (x != "") && (y != "") &&
                         (lat != "") && (lon != "") )
                    {
                        x_list.append(x);
                        y_list.append(y);
                        if ( utm_zone == "A" )
                        {
                            lon_list.append(lon);
                            lat_list.append(lat);
                        }
                        else
                        {
                            double qlat = 0, qlon = 0;
                            UTMtoLL( 23, lat.toDouble(),
                                         lon.toDouble(),
                                         utm_zone.toAscii(),
                                         qlat,qlon);
                            lon_list.append( QString("%1").arg(qlon));
                            lat_list.append( QString("%1").arg(qlat));
                        }
                    }
                }
                int np = x_list.count();
                if ( np >= 3 )
                {
                    new_pointsXX.clear();
                    new_pointsLL.clear();
                    for (int p = 0; p < np; p++ )
                    {
                        new_pointsXX.append( QPoint( x_list.at(p).toDouble(),
                                                     y_list.at(p).toDouble()) );
                        new_pointsLL.append( QPointF( lon_list.at(p).toDouble(),
                                                     lat_list.at(p).toDouble()) );
                    }
                    ret = true;
                } // if al least 3 points
            } // if Calibration section found
        } // if open ok
    } // if file exists
    return ret;
}
